<?php

    define('MTYPE_PARENT', 'parent');
    define('MTYPE_CHILDLIST', 'childattribute');

    class ReaderClass
    {
        public $attribute;
        public function __get($attrname)
        {
            switch($attrname)
            {
                case 'value':
                    $value = & $this->attribute['value'];
                    $editMode = $this->attribute['editmode'];
                    $type = $this->attribute['type'];
                    if($editMode == MTYPE_PARENT)
                    {
                        if(is_numeric($value))
                        {
                            if($value != null)
                            {
                                global $mikron;
                                $id = (int)$value;
                                $value = new $type($id);
                            }
                        }
                    }
                    if(is_object($value) && ($value instanceof pre_entity))
                    {
                        global $mikron;
                        $this->attribute['readed'] = true;
                        if(is_null($value->id))
                        {
                            // childlist-атрибут
                            $value = $mikron->Queries->Query($value->type, null, $value->cond, null, null, $value->entity);
                        }
                        else
                        {
                            // parent-атрибут
                            $type = $value->type;
                            $value = new $type($value->id);
                        }
                    }
                    return $value;
                    break;
                case 'isnull':
                    return is_null($this->attribute['value']);
                    break;
                case 'readed':
                    return $this->attribute['readed'];
                    break;
                case 'error':
                    return $this->attribute['error'];
                    break;
                case 'modified':
                    return $this->attribute['modified'];
                    break;
                default:
                    throw new Exception("Отсутствует считываемый аттрибут `$attrname`.");
                    break;
            }
        }
        public function __set($attrname, $value)
        {
            switch($attrname)
            {
                case 'modified':
                    $this->attribute['modified'] = $value;
                    break;
                case 'error':
                    $this->attribute['error'] = $value;
                    break;
                case 'value':
                    if($this->attribute['value'] === $value)
                    {
                        return;
                    }
                    if($this->attribute['editmode'] == MTYPE_PARENT)
                    {
                        if(!is_object($value) && !is_null($value))
                        {
                            $value = (int)$value;
                        }
                    }
                    $this->attribute['value'] = $value;
                    $this->attribute['modified'] = true;
                    break;
                default:
                    throw new Exception("Отсутствует записываемый аттрибут `$attrname`.");
                    break;
            }
        }
    }

    class MikronBaseObjectFunctions
    {

        private $id;
        // Кэш аттрибутов сущностей
        private $tostringcol;
        private $DisplayNames = Array();

        public function DisplayName_function($object)
        {
            $classname = get_class($object);
            if(array_key_exists('title', $object->fields))
            {
                $defaultdisplayname = VARQUOTESTART.'TITLE'.VARQUOTEEND;                    
            }
            else
            {
                $defaultdisplayname = VARQUOTESTART.'ID'.VARQUOTEEND;
            }
            if(count($this->DisplayNames) == 0)
            {
                $r = @mysql_query('SELECT `name`, `displayname` FROM ENTITIES');
                while($row = mysql_fetch_object($r))
                {
                    $name = $row->name;
                    $displayname = $row->displayname;
                    if($displayname == null)
                    {
                        $displayname = $defaultdisplayname;
                    }
                    $this->DisplayNames[$name] = $displayname;
                }
            }
            if(!isset($this->DisplayNames[$classname]))
            {
                $this->DisplayNames[$classname] = $defaultdisplayname;
            }
            return $this->toString_function($object, false, false, true);
        }

        public function toString_function($object, $for_search_result = false,
                $forurl = false,
                $fordisplayname = false,
                $extAttributes = null)
        {

            global $mikron;

            ob_start();
            foreach($mikron->Handlers->Handlers(HANDLER_TOSTRING) as $handler)
            {
                if(in_array(get_class($object), $handler->entities))
                {
                    $processor = $handler->processor;
                    $processor_object = is_object($handler->object)?$handler->object:$this->mikron->Site;
                    if(!$processor_object->$processor($object)){return ob_get_clean();}
                }
            }
            ob_get_clean();

            if(is_null($this->tostringcol))
            {
                $tsc = $mikron->Queries->Query('T_ENTITY_TEMPLATE');
                while($tse = $tsc->fetch())
                {
                    $this->tostringcol[] = $tse;
                }
            }
            $method = 1;
            if($forurl)
            {
                $method = 4;
            }
            $classname = get_class($object);
            if($fordisplayname)
            {
                $method = 5;
            }
            elseif(count($this->tostringcol) < 1)
            {
                return $classname.'('.$this->id.')';
            }
            $template = null;
            $readed_field_names = Array();
            foreach($object->fields as $field)
            {
                $fieldname = $field['name'];
                // $fieldtype = $field['type'];
                $editMode = $field['editmode'];
                switch($editMode)
                {
                    case MTYPE_CHILDLIST:
                        break;
                    case MTYPE_PARENT:
                        $readed_field_names[$fieldname] = Array('isobject' => true);
                        break;
                    default:
                        $readed_field_names[$fieldname] = Array('isobject' => false);
                        break;
                }
            }

            // reset($object->fields);
            switch($method)
            {
                case 5: // displayname
                    $template = $this->DisplayNames[$classname]; // заменяются только инструкции базовых типов
                    break;
                default:
                    foreach($this->tostringcol as $tse)
                    {
                        if(($tse->entityname->value == $classname) && ($tse->formethod->value == $method))
                        {
                            $condition = $tse->entitycondition->value;
                            if(is_null($condition))
                            {
                                $template = $tse->template->value;
                            }
                            else
                            {
                                // расчет аттрибута condition
                                $condition = str_replace(VARQUOTESTART."ID".VARQUOTEEND, $object->id, $condition);
                                if(!is_null($extAttributes) && is_array($extAttributes))
                                {
                                    foreach($extAttributes as $fieldname => $value)
                                    {
                                        $r_instruction = VARQUOTESTART.strtoupper($fieldname).VARQUOTEEND;
                                        $condition = str_replace($r_instruction, $value, $condition);
                                    }
                                }
                                foreach($readed_field_names as $fieldname => $extra_attr)
                                {
                                    $r_instruction = VARQUOTESTART.strtoupper($fieldname).VARQUOTEEND;
                                    if(($r_pos = strpos($condition, $r_instruction)) !== false)
                                    {
                                        $value = $object->$fieldname->value;
                                        if(is_null($value))
                                        {
                                            $value = 'null';
                                        }
                                        elseif(is_object($value))
                                        {
                                            $value = $value->displayname();
                                        }
                                        $condition = str_replace($r_instruction, $value, $condition);
                                    }
                                }
                                if(eval("return ($condition);"))
                                {
                                    $template = $tse->template->value;
                                    break;
                                }
                            }
                        }
                    }
                    break;
            }
            
            $template = str_replace(VARQUOTESTART.'ID'.VARQUOTEEND, $object->id, $template);

            reset($readed_field_names);
            foreach($readed_field_names as $fieldname => $extra_attr)
            {
                $r_instruction = VARQUOTESTART.strtoupper($fieldname).VARQUOTEEND;
                if(($r_pos = strpos($template, $r_instruction)) !== false)
                {
                    $field = $object->fields[$fieldname];
                    $fieldtype = $field['type'];
                    $basetype = getbasetype($fieldtype);
                    try
                    {
                        $value = $object->$fieldname->value;
                    }
                    catch(Exception $ex)
                    {
                        $value = '<font color="red">'.$ex->getMessage().'</font>';
                    }
                    $replacevalue = null; // значение, которым будет заменяться инструкция
                    if(is_null($value))
                    {
                        $replacevalue = '';
                    }
                    elseif(is_object($value))
                    {
                        $replacevalue = $value->DisplayName();
                    }
                    else
                    {
                        switch($basetype)
                        {
                            case 'picture':
                                if($value != "")
                                {
                                    $replacevalue = sprintf('<img src="%s" alt="">', $value);
                                }
                                else
                                {
                                    $replacevalue = '<img src="" alt="">';
                                }
                                break;
                            case 'file':
                                if($value != '')
                                {
                                    $name = basename($_SERVER['DOCUMENT_ROOT'].$value);
                                    $replacevalue = sprintf('<a href="%s">%s</a>', $value, $name);
                                }
                                break;
                            case 'datetime':
                                $replacevalue = date($mikron->Constants->DateFormatToString, strtotime($value));
                                break;
                            case 'text':
                                $replacevalue = $value;
                                break;
                            default:
                                $replacevalue = $value;
                                break;
                        }
                    }
                    if(!is_null($replacevalue))
                    {
                        $template = str_replace($r_instruction, $replacevalue, $template);
                    }
                }
            }
            return $template;
        }
    }

    class MikronBaseObject 
    {

        private $attributes = Array();
        static $reader;
        static $base;
        
        private $fields;
        private $id;
        public $mikron = Array('readed'=>null, 'order'=>null, 'state'=>null, 'displayname'=>null, 'error'=>null);
        private static $defaultValuesCache = Array();

        private static function getPredefinedValue($default, &$value)
        {
            global $mikron;
            switch($default)
            {
                case 'now()':
                    $value = date($mikron->Constants->DateTimeFormatMySQL);
                    break;
                case 'mikron:user()':
                    if(!is_null($mikron->Users->CurrentUser()->user))
                    {
                        $value = $mikron->Users->CurrentUser()->id();
                    }
                    break;
                case 'php:session()':
                    if(isset($_SESSION))
                    {
                        $value = session_id();
                    }
                    break;
                default:
                    return false;
            }
            return true;
        }

        function SetId($id)
        {
            $this->id = is_null($id)?null:(int)$id;
            $this->UpdateChildLists();
        }

        private function UpdateChildLists()
        {
            $isnew = is_null($this->id) || ($this->id == 0);
            foreach($this->fields as $field_name => $field)
            {
                $editMode = $field['editmode'];
                if($editMode == MTYPE_CHILDLIST)
                {
                    $fieldType = $field['type'];
                    $fieldType = $field['type'];
                    $editMode = $field['editmode'];
                    $childattribute = $field[MTYPE_CHILDLIST];
                    $id = $isnew ? 0 : $this->id; // Здесь должен быть именно 0, 
                                                  // иначе будет некорректный запрос вида
                                                  // WHERE user =
                                                  // т.е. пусто =)
                    $cond = "`$childattribute` = ".$id;
                    $value = new pre_entity($fieldType, $cond, $fieldType);
                    $this->attributes[$field_name] = Array('value' => $value, 'readed' => false, 'modified' => !is_null($value), 'error' => null, 'type'=>$fieldType, 'editmode'=>$editMode);
                }
            }
        }

        function SetBaseAttributes($id, $mikron_state, $mikron_order, $mikron_displayname)
        {
            $this->id = (int)$id;
            $this->mikron['state'] = (int)$mikron_state;
            $this->mikron['order'] = (int)$mikron_order;
            $this->mikron['displayname'] = $mikron_displayname;
        }
        
        // вынесенные функции

        public function DisplayName()
        {
            return self::$base->DisplayName_function($this);
        }
        
        public function toURL()
        {
            return self::$base->toString_function($this, false, true);
        }
        
        public function toString()
        {
            try
            {
                return self::$base->toString_function($this);
            }
            catch(Exception $ex)
            {
                echo sprintf('<font color="red">Ошибка метода toString() объекта %s<sup>%s</sup>.</font>', get_class($this), $this->id);
            }
        }

        public function __construct($mysql_row, $type, & $fields)
        {
            global $mikron;
            if(is_numeric($mysql_row))
            {
                $result = mysql_query("SELECT * FROM $type WHERE `id` = ".$mysql_row);
                if(mysql_num_rows($result) != 1)
                {
                    throw new Exception('Ошибка загрузки объекта #id='.var_export($mysql_row, true));
                }
                $mysql_row = mysql_fetch_object($result);
            }
            $isnew = is_null($mysql_row);
            $this->fields = & $fields;
            $entityname = $type;
            if(is_null(self::$reader))
            {
                self::$reader = new ReaderClass();
                self::$base = new MikronBaseObjectFunctions();
            }
            /*** Crash: If row is null. ***/
            if(!is_null($mysql_row))
            {
                $this->SetBaseAttributes($mysql_row->id, $mysql_row->mikron_state, $mysql_row->mikron_order, $mysql_row->mikron_displayname);
            }
            $this->UpdateChildLists();
            foreach($fields as $field_name => $field)
            {
                $value = null;
                $editMode = $field['editmode'];
                if($editMode == MTYPE_CHILDLIST)
                {
                }
                elseif($isnew)
                {
                    // если объект новый, тогда заполняем поля дефолтовыми значениями
                    $default = $field['default'];
                    $fieldType = $field['type'];
                    if(!is_null($default))
                    {
                        // Если значемнием по умолчанию не является спецкоммандой
                        if(!self::getPredefinedValue($default, $value))
                        {
                            // определение значения по умолчанию для атрибутов
                            $value = $default;
                            if(($editMode == MTYPE_PARENT) || ($fieldType == 'int') || ($fieldType == 'bool'))
                            {
                                if(!is_null($value))
                                {
                                    $value = (int)$value;
                                }
                            }
                        }
                    }
                    $this->attributes[$field_name] = Array('value' => $value, 'readed' => false, 'modified' => !is_null($value), 'error' => null, 'type'=>$fieldType, 'editmode'=>$editMode);
                }
            }
            if($isnew)
            {
                return;
            }
            foreach($mysql_row as $key => $value)
            {
                // Если поле служебное
                if(in_array($key, Array('id', 'mikron_order', 'mikron_state', 'mikron_displayname')))
                {
                }
                else
                {
                    $field = $fields[$key];
                    $fieldname = $key;
                    $editMode = $field['editmode'];
                    $fieldType = $field['type'];
                    $childattribute = $field[MTYPE_CHILDLIST];
                    if($editMode == MTYPE_PARENT)
                    {
                        $child_id = $value;
                        if(is_int($child_id))
                        {
                            try
                            {
                                $value = new pre_entity($fieldType, null, null, $child_id);
                            }
                            catch(Exception $ex)
                            {
                                $value = null;
                            }
                        }
                    }
                    elseif($editMode == MTYPE_CHILDLIST)
                    {
                        throw new Exception('Can\'t mapping childlist.');
                    }
                    else
                    {
                        if(in_array($fieldType, Array('int', 'boolean')))
                        {
                            $value = (int)$value;
                        }
                    }
                    $this->attributes[$key] = Array('value' => $value, 'readed' => true, 'modified' => false, 'error' => null, 'type'=>$fieldType, 'editmode'=>$editMode);
                }
            }
        }

        public function __get($attrname)
        {
            if(array_key_exists($attrname, $this->attributes))
            {
                self::$reader->attribute = & $this->attributes[$attrname];
                return self::$reader;
            }
            elseif($attrname == 'id')
            {
                return $this->id;
            }
            elseif(in_array($attrname, Array('isNew', 'isnew')))
            {
                return is_null($this->id) || ($this->id == 0);
            }
            throw new Exception("Отсутствует аттрибут `$attrname`.");                
        }

        public function Validate()
        {
            return self::ValidateFunction($this);
        }

        private static function ValidateFunction($object)
        {
            global $mikron;
            $errors_count = 0;
            foreach($object->fields as $field)
            {
                
                $field_name = $field['name'];
                $fieldType = $field['type'];
                $field_editmode = $field['editmode'];
                $field_dbtype = $field['dbtype'];
                $field_validator = $field['validator'];

                $field_dbtype_base = strtolower(getbasetype($field_dbtype));
                $fieldType_base = strtolower(getbasetype($fieldType));
                $field_value = null;

                $allownull = $field['allownull'];
                if(!$allownull)
                {
                    if($mikron->Tools->IsNullOrEmpty($object->$field_name->value))
                    {
                        $object->$field_name->error = 'Обязательное поле.';
                        $errors_count++;
                    }
                }

                if(in_array($field_editmode, Array(MTYPE_PARENT, MTYPE_CHILDLIST)))
                {
                    continue;
                }
                elseif(($field_value = $object->$field_name->value) === null)
                {
                    continue;
                }
                elseif(($object->$field_name->modified) || ($fieldType == 'datetime'))
                {
                    switch(strtolower($field_dbtype_base))
                    {
                        case 'int':
                            if(!is_numeric($field_value) && !$data_is_null)
                            {
                                return $mikron->Exceptions->InvalidDataException($field);
                            }
                            if($field_value !== null)
                            {
                                $field_value = (int)$field_value;
                            }
                            $object->$field_name->value = $field_value;
                            break;
                        case 'datetime';
                            if(($field_value = date($mikron->Constants->DateTimeFormatMySQL, strtotime($field_value))) == -1)
                            {
                                return $mikron->Exceptions->InvalidDataException($field);
                            }
                            $object->$field_name->value = date($mikron->Constants->DateTimeFormatMySQL, strtotime($field_value));
                            break;
                    }
                    switch($fieldType_base)
                    {
                        case 'boolean':
                            if(!is_numeric($field_value) && !is_bool($field_value))
                            {
                                return $mikron->Exceptions->InvalidDataException($field);
                            }
                            $object->$field_name->value = $field_value ? 1 : 0;
                            break;
                    }
                }
            }
            return $errors_count == 0;
        }

        /**
        * Сохранение сущности. Метод выясняет модифицированные поля
        * и если такие есть, то генерирует SQL-код сохранения,
        * либо обновления записи в базе данных.
        * 
        * @param mixed $always If there is nothing to save then save display_name only.
        */
        public function Save($always = false)
        {

            global $mikron;

            // Валидация данных
            if(($exception = $this->Validate()) !== true)
            {
                // Генерируется исключение при ошибках валидации
                throw new Exception($exception->Description);
            }

            $entityname = get_class($this);
            $fields = Array();
            $values = Array();
            $fieldsandvalues = Array();

            // Сохраняемые пары: [Имя атрибута] => [Значение]
            $data_for_save = Array();

            // Define of the list of saved attributes
            foreach($this->fields as $field)
            {
                $fieldname = $field['name'];
                $editMode = $field['editmode'];
                if($this->$fieldname->readed || $this->$fieldname->modified)
                {
                    if($editMode == MTYPE_CHILDLIST)
                    {
                        // Save childlists
                        if(is_array($field_array = $this->$fieldname->value))
                        {
                            foreach($field_array as $child)
                            {
                                $child->Save();
                            }
                        }
                    }
                    elseif($editMode == MTYPE_PARENT)
                    {
                        // Save parent
                        if(is_object($this->$fieldname->value))
                        {
                            $this->$fieldname->value->Save();
                            $data_for_save[$fieldname] = $this->$fieldname->value->id;
                        }
                        elseif(is_null($this->$fieldname->value))
                        {
                            $data_for_save[$fieldname] = null;
                        }
                    }
                    elseif($this->$fieldname->modified)
                    {
                        // Save other types
                        if($editMode == 'password')
                        {
                            if(!$this->$fieldname->isnull)
                            {
                                if(strlen($this->$fieldname->value) != 32)
                                {
                                    $this->$fieldname->value = md5($this->$fieldname->value);
                                }
                            }
                        }
                        $data_for_save[$fieldname] = $this->$fieldname->value;
                    }
                }
            }

            if((count($data_for_save) < 1) && !$always)
            {
                // If nothing to save/Если нечего сохранять
                return null;
            }
            $displayname = quote_smart($this->DisplayName());
            $data_for_save['mikron_displayname'] = $displayname;
            if($this->isNew)
            {
                $mikron->Queries->SaveNewEntity($this, $data_for_save);
            }
            else        
            {
                $mikron->Queries->SaveEditedEntity($this, $data_for_save);
            }
        }
        
        /**
        * Функция убирает признаки модификации атрибутов,
        * 
        * @param mixed $object Объект, над атрибутами которого необходимо провести действие.
        */
        public static function DropModifiedFlags($object)
        {
            // Убираем признаки модификации атрибутов,
            // т.к. мы только что синхронизовали объект с записью в БД.
            foreach($object->fields as $field)
            {
                $fieldname = $field['name'];
                if($object->$fieldname->modified)
                {
                    $object->$fieldname->modified = false;
                }
            }
        }

    }

?>